--水果机

local _ENV = hf_setenv('tigertable',true)

--------------------------------------------------------------------
local skynet = require "skynet"
local utils = require "utils"
local proxypack = require "proxypack"
local msgdef = require "msgdef"
local pb = require 'protobuf'
local cjson = require "cjson"
local server_def = require "server_def"
local util = require "util"
local table_def = require "table_def"
local sharedata = require "skynet.sharedata"
local error_code = require "error_code"
local reason = require "reason"
local tiger = require "lhj.tiger"
local game_def = require "game_def"
local cocall = require "cocall"
local constant = require "constant"
local system_store = require "system_store"
local expand = require("expand") --老游戏功能拓展

local table_insert = table.insert
local table_remove = table.remove
local table_sort   = table.sort
local table_unpack = table.unpack
local math_floor = math.floor
local string_format = string.format
local math_random = math.random
local math_abs = math.abs
------------------------------游戏状态------------------------------
local TABLE_STATUS_CHECK_START    = 1     --游戏开始
local TABLE_STATUS_WAITTING_BET   = 2     --游戏开始
local TABLE_STATUS_GAMEOVER       = 3  
local TABLE_STATUS_GAMEOVER_WAIT  = 4
local TABLE_STATUS_RESTART        = 5

------------------------------常量定义------------------------------
local MAX_PLAYER_NUM            = 1
local REGISTER_CLIENT_FD        = 0
local OFFLINE_CLIENT_FD         = -2

------------------------------模块初始化-----------------------------------
function init()
    global_configs              = nil
    shapedata                   = nil
    table_players               = {}      --标记房间所有玩家的clientId 包括观战者的clientId
    player_info_list            = {}      --管理房间内所有的玩家列表
    curr_player_uid             = nil     --老虎机当前只有一个玩家，记录该玩家的id
    reconnect_timout            = {}      --重连超时时间

    closing_server              = false   --关服务器
    stopping_server             = false   --停止服务器

    curr_tiger_instance         = nil
    on_matching                 = false   --是否正在匹配

    curr_status                 = nil
    curr_round                  = nil

    game_start_init_time        = nil

    store_result                = 0       --库存返回结果
    control_uid                 = 0
    err_result                  = 0

    self_table_type             = nil
    self_table_id               = nil
    self_table_gid              = nil
    self_table_pattern          = nil
    self_game_type              = nil

    table_water                 = 0         --桌子上的玩家流水
    table_award_coins           = 0         --玩家获奖总额
    table_total_bet             = 0         --玩家启动消耗总额
    err_result                = 0
    uid_betting_map = {}

    --触发财神驾到的金额
    caishen_coins_map = {}

    hf_set_sentinel_end(_ENV)
end

local handler = {}
local internal = {}

local function get_extra_bill_info(uid)
    local t = {game_type=self_game_type,table_type=self_table_type}
    local player_info = player_info_list[uid]
    if not player_info then
        return t
    end

    t.client_version = player_info.client_version
    t.channel = player_info.channel
    t.cur_channel = player_info.cur_channel
    t.device_id = player_info.device_id
    t.device_brand = player_info.device_brand
    t.os_system = player_info.os_system
    t.platform = player_info.platform
        
    return t
end

local function get_player_num()
    local n = 0
    for _,_ in pairs(table_players) do
        n = n + 1
    end
    return n
end

local function set_on_match()
    on_matching = true
end

local function unset_on_match()
    on_matching = false
end

local function is_on_match()
    return on_matching
end

local function lock_one_player(uid)
    local ok,succ = R().exdbsvr(1):call('.tlock_mgr','set_on_table',uid,self_table_gid,self_table_gid)
    if not ok then
        errlog(string_format("uid(%d) self_table_gid(%d) failed to set_on_table", uid, self_table_gid))
        return false
    end

    if not succ then
        return false
    end
  
    return true
end

--玩这实时输赢op
--有 leave_time = 真正离开游戏了 
function realTimeWinLose(uid, leave_time)
    local player_info = player_info_list[uid]
    if not player_info then return end
    if not player_info.enter_time then 
        errlog("__________player_info.enter_time__是空的____", uid, player_info)
        return 
    end        
    local table_gid = player_info.enter_time .. "_" .. self_table_id .."_"..uid
    leave_time = leave_time or 0
    if player_info.all_win_lose == 0 and player_info.all_free == 0 then 
        --没产生流水就不记录了
        return
    end
    local data = {
        op = "user_leave_game",
        uid = uid,
        table_gid = table_gid, --唯一标记
        table_type = self_table_type,
        name = player_info.name,        
        enter_coins = player_info.enter_coins, --进入时身上金币
        enter_safe_coins = player_info.enter_safe_coins, --进入时保险箱金币 
        enter_time = player_info.enter_time, --进入时间
        leave_time = leave_time, --离开时间
        leave_coins = player_info.coins, --离开时身上金币
        leave_safe_coins = player_info.safe_box, --离开时保险箱金币
        win_lose = player_info.all_win_lose, --在玩游戏内产生的输赢（
        free = player_info.all_free, --玩游戏交上的台费
    }
    dbglog("_______user_leave_game_____",  data, leave_time)
    billlog(data)
end



local function unlock_one_player(uid)
    realTimeWinLose(uid, os.time())
    R().exdbsvr(1):send('.tlock_mgr','unset_on_table',uid,self_table_gid)
end

local function get_player_enter_data(uid)
    --先拉取玩家信息
    local ok,base_data = R().basesvr{key=uid}:call('.msg_handler','get_base_data',uid)
    if not ok then
        errlog(string_format("uid(%d) call basesvr get_base_data error", uid))
        return
    end

    --再拉取玩家的登入信息 跟大厅拉取
    local ok,enter_data = R().hallsvr{key=uid}:call('.msg_handler','get_enter_data',uid)
    if not ok then
        errlog(string_format("uid(%d) call hallsvr get_enter_data error", uid))
        enter_data = {}
    end

    dbglog(string_format("base_data(%s) enter_data(%s)", tostring_r(base_data), tostring_r(enter_data)))

    local free_times_data = base_data.free_times_data
    local free_times_info = free_times_data[1]

    local last_bet_score = 0
    local last_bet_line = 1

    if free_times_info then
        last_bet_score = free_times_info.last_bet_score or 0
        last_bet_line = free_times_info.last_bet_line or 1
    end

    local player_info = {
        uid = base_data.uid,
        name = enter_data.name or '',
        coins = base_data.coins,
        icon = enter_data.icon or '',
        sex = enter_data.sex,
        vip_level = base_data.vip_level or 0,
        icon_border = enter_data.icon_border,
        free_times = base_data.total_free_times or 0,
        last_bet_score = last_bet_score,
        last_bet_line = last_bet_line,
        free_times_round = 0,
        safe_box = base_data.safe_box,
     }

    return player_info
end

local function notify_matchsvr_table_stats(is_delete)
    local table_stats = {}
    if is_delete then
        table_stats.table_gid = self_table_gid
        table_stats.table_type = self_table_type
        R().newmatchsvr(1):send('.table_mgr','delete_table_stats',table_stats)
    else
        table_stats.table_type = self_table_type
        table_stats.left_num = table_def.game_player_num[self_game_type] - get_player_num()
        table_stats.table_gid = self_table_gid
        table_stats.table_pattern = self_table_pattern

        R().newmatchsvr(1):send('.table_mgr','update_table_stats',table_stats)
    end
end

--当桌子上没有玩家时，改变桌子状态,避免别的玩家进入该桌子时使用上一个玩家instance中的数据
-- local function change_table_statue()
--     print("========55555555change_table_statue=====")
--     curr_tiger_instance = nil
--     curr_status = TABLE_STATUS_CHECK_START
-- end

--把输赢发送到好友服(平台游戏概况)
local function sendLoseWinToFriendServer(system_lose_win)  
    if system_lose_win == 0 then return end             
    R().friendsvr(1):send('.info_mgr', 'set_room_summary', self_table_type, {table_type = self_table_type, lose_win = system_lose_win})
end  

local function kick_extra_player(uid)
    unlock_one_player(uid)
    table_players[uid] = nil
    player_info_list[uid] = nil
    curr_player_uid = nil
    --change_table_statue()
end

local function clear_and_unlock_offline_player(uid)
    R().friendsvr(1):send('.info_mgr','update_info',uid,{curr_game_type = 0})
    kick_extra_player(uid)
end

-- local function clear_offline_players()
--     for uid, status in pairs(table_players) do
--         if status == OFFLINE_CLIENT_FD then
--             print("===========66666666",uid)
--             local ok, ret = xpcall(clear_and_unlock_offline_player, debug.traceback, uid)
--             if not ok then
--                 errlog(uid, ret)
--             end
--         end
--     end
-- end

local function send_to_gateway(uid,client_fd,...)
    if not client_fd or client_fd <= 0 then
        return
    end

    return utils.send_to_gateway(0,uid,client_fd,...)
end

local function watch_session(game_session,uid,observing)
    if game_session <= 0 then
        return
    end
    local gateway_id = game_session >> 31
    local request
    if observing then
        request = 'observe_fd'
    else
        request = 'unobserve_fd'
    end

    R().gateway(gateway_id):send('.watchdog','tablesvr',request,R().get_source(),game_session,uid)
end

local function notify_others(action,excepted_uid,msg)
    --先广播给桌子上的人
    for uid, fd in pairs(table_players) do
        if uid ~= excepted_uid then
            send_to_gateway(uid,fd,action,msg)
        end
    end
end

local function notify_all(action,msg)
    return notify_others(action,nil,msg)
end

local function check_start(curr_time)
    if curr_tiger_instance then
        return false,-1
    end    
    if not curr_player_uid then
        return false,-2
    end
    local instance = tiger:new()
    if not instance then
        dbglog('failed to can_start()')
        return false
    end

    instance:init(curr_player_uid)
    instance:start_game()

    print('tiger now start the game...')
    curr_tiger_instance = instance
    return true
end

local function get_response_player_info(player_info)
    return {
        uid = player_info.uid,
        name = player_info.name,
        coins = player_info.coins,
        icon = player_info.icon,
        sex = player_info.sex,
    }
end

local function add_coins(uid,coins,reason,...)
    local ok,succ,ret = R().basesvr{key=uid}:call('.msg_handler','add_coins',uid,coins,reason,...)
    if not ok then
        errlog(uid,'failed to add_coins ok is nil',coins)
        return
    end
    if not succ then
        errlog(uid,'failed to add_coins succ is nil',coins)
        return
    end

    R().basesvr{key=uid}:send('.msg_handler','sub_lose_coins',uid,coins)
    
    return {
        curr_coins = ret.curr,
    }
end

local function reduce_coins(uid,cost_coins,reason,...)
    print("================cost_coins================",cost_coins)
    local ok,succ,ret = R().basesvr({key=uid}):call('.msg_handler','reduce_coins',uid,cost_coins,reason,...)
    if not ok then
        errlog(uid, "lhj reduce_coins error because ok is nil")
        return
    end

    if not succ then
        errlog(uid, "lhj reduce_coins error because succ is nil")
        return
    end

    R().basesvr{key=uid}:send('.msg_handler','add_lose_coins',uid,cost_coins)

    return {
        curr_coins = ret.curr
    }
end

local function add_free_times(uid,times,reason,...)
    local player_info = player_info_list[uid]
    local free_tiems_info = {
        times = times,
        last_bet_score = player_info.last_bet_score or 0,
        last_bet_line = player_info.last_bet_line or 1
    }
    local ok,succ,ret = R().basesvr{key=uid}:call('.msg_handler','add_free_times',uid,free_tiems_info,reason,...)
    if not ok then
        errlog(uid,'failed to add_free_times ok is nil',times)
        return
    end
    if not succ then
        errlog(uid,'failed to add_free_times succ is nil',times)
        return
    end
    
    return true,ret
end

local function reduce_free_times(uid,times,reason,...)
    print("================reduce_free_times================",times)
    local ok,succ,ret = R().basesvr({key=uid}):call('.msg_handler','reduce_free_times',uid,times,reason,...)
    if not ok then
        errlog(uid, "lhj reduce_free_times error because ok is nil")
        return
    end

    if not succ then
        errlog(uid, "lhj reduce_free_times error because succ is nil")
        return
    end

    return true,ret

end

--获得奖金池的总金额
local function get_curr_caijin()
    local curr_caijin = 0
    local ok,ret = R().exdbsvr(1):call('.fruit_mgr','get_unreal_jackpot',self_table_type)
    if not ok then
        errlog(string_format("call exdbsvr get_unreal_jackpot not ok", self_game_type, self_table_id))
    else
        curr_caijin = ret
    end

    return curr_caijin
end

local function get_enter_info(uid)
    local player_info = player_info_list[uid]
    local rsp_player_info = get_response_player_info(player_info)

    local curr_caijin = get_curr_caijin()
    local bet_range = {}
    local score = {}
    for _, value in pairs(global_configs.tiger_rate.bet_range) do
        table_insert(score,value)
    end
    bet_range.score = score
    local enter_info = {
        caijin_coins = curr_caijin,             --彩金（奖金）
        total_award_coins = 0,                  --累积中奖金额（进入桌子开始）
        free_times = player_info.free_times,     --免费下注次数
        last_bet_score = player_info.last_bet_score,
        last_bet_line = player_info.last_bet_line,
        player_info = rsp_player_info,          --当前玩家信息
        bet_range = bet_range            
    }
    dbglog(tostring_r(enter_info))
    local roomdata = global_configs.roomdata[self_table_type]
    return {
        game_type = self_game_type,
        table_type = self_table_type,
        tiger_enter_info = enter_info,
        name = roomdata.name,
    }
end

local function enter(uid,client_fd,msg)
    if not table_players[uid] then
        errlog(uid,'this player had not been registerd!!!')
        return false,-431
    end
    --老虎机房间只能有一个人
    if curr_player_uid and curr_player_uid ~= uid then
        errlog(uid,'curr_player_uid exist!!!')
        kick_extra_player(uid)
        return false,-432
    end

    table_players[uid] = client_fd
    curr_player_uid = uid
    reconnect_timout[uid] = nil

    local player_info = assert(player_info_list[uid])
    
    local data = get_player_enter_data(uid)
    if data then
        player_info.name = data.name
        player_info.icon = data.icon
        player_info.coins = data.coins
        player_info.free_times = data.free_times
        player_info.last_bet_score = data.last_bet_score
        player_info.last_bet_line = data.last_bet_line
        player_info.free_times_round = data.free_times_round
        player_info.safe_box = data.safe_box
        player_info.enter_coins = data.coins
        player_info.enter_time = os.time()
        player_info.enter_safe_coins = data.safe_box
        player_info.all_win_lose = 0
        player_info.all_free = 0
        player_info.all_produce = 0
        player_info.all_bet = 0


    else
        errlog(uid,'failed to get_player_enter_data')
    end
    player_info.os_system = data.os_system
    player_info.platform = data.platform
    player_info.client_version = msg.client_version
    player_info.channel = msg.channel
    player_info.cur_channel = msg.cur_channel
    player_info.device_id = msg.device_id
    player_info.device_brand = msg.device_brand

    local rsp_enter_info = get_enter_info(uid)

    dbglog('--player enter --- ',uid)
    return true,rsp_enter_info
end

local function get_player_curr_coins(uid)
    if player_info_list[uid] then
        return player_info_list[uid].coins
    end

    local ok,base_data = R().basesvr({key=uid}):call('.msg_handler','get_base_data',uid)
    if not ok then
        errlog(uid,'get_player_curr_coins-->failed to get base_data')
        return 0
    end

    return base_data.coins
end

local function notify_players_coins_change(uid,curr_coins)
    local money_changed_reason = constant.MONEY_CHANGED_REASON.TIGER_BET
    R().hallsvr{key=uid}:send('.msg_handler','toagent',uid,'notify_money_changed',{coins = curr_coins},money_changed_reason)

    local player_info = player_info_list[uid]
    if player_info then
        player_info.coins = curr_coins
    end
end

local function write_card_record_log(win_coins,pay_fee,rsp_game_result,info)
    local bet_score = info.bet_score
    local bet_lines = info.bet_lines
    local free_bet  = info.free_bet
    local result_shape = info.result_shape
    local result_lines = info.result_lines or {}
    local caijin_coins = info.caijin_coins
    local free_times = info.free_times

    local winners = {}
    local losers = {}
    local player_win = 0

    local player_info = player_info_list[uid]
    local left_coins = 0
    if player_info then
        left_coins = player_info.coins
    end

    local system_win_lose = - win_coins + pay_fee
    sendLoseWinToFriendServer(system_win_lose)

    local str_date = os.date("%Y%m%d%H%M%S")
    local player_add_acore = 0
    if win_coins >= 0 then
        table_insert(winners,{
            uid = curr_player_uid,
            left_score = left_coins,
            add_score = win_coins-pay_fee,
            pay_fee = pay_fee,
            is_robot = false,
        })
    else
        table_insert(losers,{
            uid = curr_player_uid,
            left_score = left_coins,
            add_score = win_coins,
            pay_fee = pay_fee,
            is_robot = false,
        })
    end

    player_win = win_coins - pay_fee    

    local lines = {}
    for _,v in pairs(result_lines) do
        table_insert(lines,{line_num = v.shape_num,line_times = v.line_times})
    end

    local shapes = {}
    for k,v in pairs(result_shape) do
        table_insert(shapes,v.shape)
    end
    local exinfo = {
        bet_score = bet_score,
        bet_lines = bet_lines,
        free_times = free_times,
        caijin_coins = caijin_coins,
        result_shape = shapes,lines = lines,
        add_score = player_win,
        system_result = store_result,
        control_uid = control_uid,
        err_result = err_result or 0,
    }
    billlog({ op = "card_record", 
        table_gid = str_date .. "_" .. self_table_id, 
        game_type = self_game_type,
        table_type = self_table_type, 
        begin_time = util.get_now_time(),
        end_time = util.get_now_time(), 
        curr_round = curr_round, 
        password = self_password,
        winner_list = winners, 
        loser_list = losers, 
        system_win = -player_win,
        side_list = {},
        exinfo = exinfo,
        total_num = 1,
        address = skynet.address(skynet.self()),
    })

    -- local curr_coins = get_player_curr_coins(curr_player_uid)
    
    R().basesvr({key=curr_player_uid}):send('.msg_handler','add_play_count',curr_player_uid,1)
    
    -- if curr_coins <= constant.POCHAN_COINS then --记录破产日志
    --     billlog({op = "bankrupt_register",uid = curr_player_uid,game_type = self_game_type,
    --         table_type = self_table_type,
    --         ret = curr_coins,
    --     })
    -- end
end

--更新系统库存
local function add_system_store(system_coins)
    local fruit_store = global_configs.fruit_store
    if not fruit_store then
        print("get config failed")
        return
    end
    if curr_tiger_instance:is_free_times() then
        print("========free bet=======",curr_player_uid,system_coins)
        return 
    end

    --受GM控制就不加系统库存
    if store_result == constant.PERSONAL_STORE_WIN or store_result == constant.PERSONAL_STORE_LOSE then
        print("add_system_store==gm_control===",store_result)
        return
    end

    local base_rate = fruit_store.store_base_sys.contribute_rate or 0
    local adjust_rate = fruit_store.store_adjust.contribute_rate or 0
    local sysback_rate = fruit_store.store_system_back.contribute_rate or 0
    local jackpot_rate = fruit_store.store_real_jackpot.contribute_rate or 0
    assert(base_rate + adjust_rate + sysback_rate + jackpot_rate == 10000)

    local base_coins = math_floor(base_rate * system_coins / 10000)
    local adjust_coins = math_floor(adjust_rate * system_coins / 10000)
    local sysback_coins = math_floor(sysback_rate * system_coins / 10000)
    local jackpot_coins = math_floor(jackpot_rate * system_coins / 10000)

    print("============fffffff",base_coins,adjust_coins,sysback_coins,jackpot_coins)
    R().exdbsvr(1):send('.fruit_mgr','push_data_pool',self_table_type,base_coins,adjust_coins,jackpot_coins,sysback_coins)
end

--根据库存选择结果
local function get_store_result()
    local result = 0
    local store_base_sys = global_configs.fruit_store.store_base_sys or {}
    local free_times = global_configs.fruit_store.free_tiems
    local bet_score,bet_lines = curr_tiger_instance:get_uid_bet_record(curr_player_uid)
    print("==============table data======",curr_player_uid,table_water,table_total_bet,table_award_coins)
    --获取库存值
    local ok,ret = R().exdbsvr(1):call('.fruit_mgr','get_fruit_store_coins',self_table_type)
    if not ok or not ret then
        print("get_fruit_store_coins failed",curr_player_uid)
        return result
    end
    local base_coins = ret.base_coins or 0

    --检查个人GM控制
    local gm_warn = (global_configs.gm_control or {}).warn_coins or 50000000
    local gm_result = system_store.get_player_personal_control_result(curr_player_uid,gm_warn)
    if gm_result > 0 then --返回constant.PERSONAL_STORE_WIN or constant.PERSONAL_STORE_LOSE
        print("get personal control is",gm_result)
        control_uid = curr_player_uid
        return gm_result
    end

    --检查触发奖池
    local fruit_store = global_configs.fruit_store or {}
    local store_real_jackpot = fruit_store.store_real_jackpot or {}
    local jackpot_coins = ret.jackpot_coins or 0
    if table_water >= (store_real_jackpot.target_turnover or 0) and jackpot_coins > (store_real_jackpot.base_warn or 0) then
        if math_random(1,10000) <= store_real_jackpot.target_rate then
            return constant.JACKPOT_STORE
        end
    end 

    --检查免费次数
    --免费次数,本局没有使用免费次数下注才有几率触发免费次数
    --触发概率为9条线的触发概率，如果是8条线，则在概率上乘以8/9.如果是1条线，则在概率上乘以1/9.
    print("========free===",curr_tiger_instance:is_free_times(),tostring_r(player_info_list[curr_player_uid]),curr_round,base_coins,store_base_sys.base_warn)
    local is_free = curr_tiger_instance:is_free_times()
    local base_min = store_base_sys.base_min or 0 --库存1下限
    local player_info = player_info_list[curr_player_uid]
    if not is_free and player_info and base_coins > base_min then
        if curr_round - (player_info.free_times_round or 0) > free_times.target_round then --上次触发免费次数后,多少局以后才能再次触发
            if math_random(1,10000) <= free_times.target_rate * (bet_lines / 9) then
                return constant.FREE_TIMES
            end
        end
    end

    --检查库存2,奖励库存
    local store_adjust = fruit_store.store_adjust or {}
    local award_coins = ret.adjust_coins or 0
    local award_min = store_adjust.award_min or 0
    local award_max = store_adjust.award_max or 0
    local tigger_rate_min = store_adjust.tigger_rate_min or 0
    local tigger_rate_max = store_adjust.tigger_rate_max or 0
    local award_tigger_rate = 0
    dbglog("get_store_result111",award_coins,award_min,award_max,tigger_rate_min,tigger_rate_max)
    if award_coins < award_min then
        award_tigger_rate = 0
    elseif award_coins >= award_min and award_coins < award_max then
        --当奖励库存处于下限值和上限值之间时  最终触发概率=触发率下限+((实时库存-下限值) /(上限值-下限值)) *(触发率上限-触发率下限)
        award_tigger_rate = (tigger_rate_min / 10000) + ((award_coins - award_min)/(award_max-award_min)) * (tigger_rate_max/10000 - tigger_rate_min/10000)
    elseif award_coins >= award_max then
        award_tigger_rate = tigger_rate_max / 10000
    end

    print("fruit_award_tigger_rate===",award_tigger_rate)
    local rand = math_random(1,10000)
    if (rand / 10000) <= award_tigger_rate then --触发奖励库存
        dbglog("check_system_store==award_lose")
        return constant.AWARD_STORE_LOSE
    end

    --检查库存1
    local store_base_sys = fruit_store.store_base_sys or {}
    local base_min = store_base_sys.base_min or 0 --库存1下限
    local base_max = store_base_sys.base_max or 0 --库存1上限
    local win_control_rate = store_base_sys.win_control_rate or 0 --吃分控制率(万分比)
    local lose_control_rate = store_base_sys.lose_control_rate or 0 --放分控制率(万分比)
    local warn_control_rate = store_base_sys.warn_control_rate or 0 --警戒控制率(万分比)
    local average_value = math_floor((base_min + base_max)/2)

    dbglog("get_store_result222",base_coins,base_min,base_max,win_control_rate,lose_control_rate,warn_control_rate,average_value)
    --实时库存低于下限值时,使用固定的警戒控制率
    if base_coins < base_min then
        local base_tigger_rate = warn_control_rate / 10000
        local rand = math_random(1,10000) / 10000
        dbglog("get_store_result333",rand,base_tigger_rate)
        if rand <= base_tigger_rate then
            return constant.SYSTEM_STORE_WIN
        end
    end
    --在下限值和平均值之间触发吃分控制率,实际吃分控制率=((平均值-实时库存) /(平均值-下限值)) * 吃分控制率
    if base_coins >= base_min and base_coins <= average_value then
        local base_tigger_rate = ((average_value - base_coins) / (average_value - base_min)) * (win_control_rate/10000)
        dbglog("get_store_result444",base_tigger_rate)
        if (math_random(1,10000) / 10000) <= base_tigger_rate then
            return constant.SYSTEM_STORE_WIN
        end
    end

    return result
end

local function select_shape()
    local ok,ret = R().exdbsvr(1):call('.fruit_mgr','get_fruit_store_coins',self_table_type)
    if not ok or not ret then
        errlog("get_fruit_store_coins failed",curr_player_uid)
    end

    local base_coins = ret.base_coins or 0
    local adjust_coins = ret.adjust_coins or 0
    local jackpot_coins = ret.jackpot_coins or 0

    local store_coins = {
        base_coins = ret.base_coins or 0,      --库存1(基础库存)
        adjust_coins = ret.adjust_coins or 0,  --库存2(奖励库存)
        jackpot_coins = ret.jackpot_coins or 0 --奖池
    }
    err_result = curr_tiger_instance:select_shape(store_result,curr_player_uid,store_coins)
end

local function send_real_horse_message(coins)
    if coins > 0 then
        local roomdata = global_configs.roomdata[self_table_type] or {}
        local exinfo = cjson.encode({game_type = self_game_type})
        local player_info = player_info_list[curr_player_uid]
        if player_info then
            R().mailsvr(1):send('.opration_message_worker','send_real_horse_message',constant.BROADCAST_IDS.BIDS_FRUIT,{name = player_info.name,coin = coins})

            R().mailsvr(1):send('.opration_message_worker','send_real_horse_message',constant.BROADCAST_IDS.GUIDE_BROAD,
                {vip_level = player_info.vip_level,name = player_info.name,game_name = roomdata.name or '',coin =  coins,exinfo = exinfo})
        end
    end
end

local function check_today_coins_water_goal(uid,data)
    local seven_reward = global_configs.seven_reward
    local goal_list = {}
    for k,v in pairs(seven_reward) do  
        table_insert(goal_list,(v.coins_low + v.coins_hight)/2)
        table_insert(goal_list,coins_hight)
    end

    local function get_section_and_high(value)
        local section,section_high
        for num,data in pairs(seven_reward) do
            if value >= data.coins_low then
                section = num
                section_high = data.coins_hight
            end 
        end

        return section,section_high
    end
    
    for _,value in pairs(goal_list) do
        if (data.pre_coins_water or 0) < value and (data.curr_coins_water or 0) >= value then
            local section,section_high = get_section_and_high(data.curr_coins_water or 0)
            local ntf = {
                today_coins   = data.curr_coins_water or 0,
                section_count = section,
                section_hight = section_high or 0,
                section_total_count = #seven_reward,
            }
            send_to_gateway(curr_player_uid,table_players[curr_player_uid],'hall.NTF_SEVEN_REWARD_GOAL',ntf)
        end
    end
end

local function add_player_coins_water(water_coins)
    local ok,ret = R().basesvr({key = curr_player_uid}):call('.msg_handler','add_player_coins_water',curr_player_uid,math_abs(water_coins))
    print("add_player_coins_waterbbbbbbbbbb",ok,tostring_r(ret))
    -- if ok and ret then
    --     check_today_coins_water_goal(curr_player_uid,ret)
    -- end
end

local function get_fruit_jackpot()
    local ok,ret = R().exdbsvr(1):call('.fruit_mgr','get_fruit_jackpot',self_table_type)
    if not ok or not ret then
        errlog("get_fruit_jackpot failed",curr_player_uid)
        return 
    end

    return ret
end

local function notify_caishenjiadao(uid,coins)
    print("=============notify_caishenjiadao",uid,coins)
    local curr_time = util.get_now_time()
    local countdown_time = global_configs.caishenjiadao.countdown_time or 10
    local end_time = curr_time + countdown_time + 10
    caishen_coins_map[uid] = coins
    send_to_gateway(uid,table_players[uid],'table.NTF_CAISHENJIADAO',
    {end_time = end_time,trigger_coins = coins,game_type = self_game_type})
    billlog({op = "caishenjiadao",type = "trigger",uid = uid,table_type = self_table_type,coins = coins})
end

local function check_store_transfer()
    local roomdata = global_configs.roomdata[self_table_type]
    if not roomdata then
        return
    end
 
    local cnf = global_configs.fruit_store
    if not cnf then
         return
    end

    local store_base_sys = cnf.store_base_sys

    local base_min  = store_base_sys.base_min or 0
    local base_max  = store_base_sys.base_max or 0
    local base_normal = store_base_sys.base_normal or 0
    local data = {base_min = base_min,base_max = base_max,base_normal = base_normal,table_type = self_table_type}
    R().exdbsvr(1):send('.fruit_mgr','check_store_transfer',self_game_type,data)
 end

--结算
local function audit_game_result(game_result)
    local bet_score,bet_lines = curr_tiger_instance:get_uid_bet_record(curr_player_uid)
    local jackpot_coins = get_fruit_jackpot() or 0      --当前奖池的钱
    local jackpor_award = math_floor(game_result.jackpot_rate/100 * jackpot_coins)     --玩家从奖池获得金额
    local lines_award = game_result.result_times * bet_score    --玩家基础连线获得金额
    local total_award = jackpor_award + lines_award
    local win_coins = math_floor(total_award - bet_score * bet_lines)    --获得奖励-启动消耗: 玩家本次下注输赢结果
    if curr_tiger_instance:is_free_times() then    --免费下注
        win_coins = math_floor(total_award)
    end

    local pay_fee = 0
    if win_coins > 0 then
        local roomdata = global_configs.roomdata[self_table_type]
        local fee_percent = (roomdata.cost or 5)/ 100
        
        pay_fee = math_floor(win_coins * fee_percent)   --（获得奖励-启动消耗）*5%  免费下注时启动消耗为0
    end
    print("=============player wind coins===",curr_tiger_instance:is_free_times(),store_result,bet_score * bet_lines,jackpot_coins,jackpor_award,lines_award,win_coins,pay_fee,total_award - pay_fee)
    
    local player_info = assert(player_info_list[curr_player_uid])
    if store_result == constant.JACKPOT_STORE then   --走奖池控制，奖池部分从奖池扣，基础连线从库存1扣
        if jackpor_award > 0 then
            print("=========1111111sub_fruit_jackpot",jackpor_award,self_table_type)
            R().mailsvr(1):send('.fruit_mgr','sub_fruit_jackpot',self_table_type,jackpor_award,player_info.name,player_info.icon)
        end
        if lines_award > 0 then
            R().exdbsvr(1):send('.fruit_mgr','sub_fruit_base',self_table_type,lines_award)
        end
    elseif store_result == constant.AWARD_STORE_LOSE then --库存2控制，从库存2扣
        if total_award > 0 then
            R().exdbsvr(1):send('.fruit_mgr','sub_fruit_adjust',self_table_type,total_award)
        end
    else  
        --受GM控制的,走GM库存不走库存1
        print("store_result=====================",store_result,total_award,win_coins)
        if store_result ~= constant.PERSONAL_STORE_WIN and store_result ~= constant.PERSONAL_STORE_LOSE then
            R().exdbsvr(1):send('.fruit_mgr','sub_fruit_base',self_table_type,total_award)
        end
        
        --更新玩家个人控制数据
        if store_result == constant.PERSONAL_STORE_WIN or store_result == constant.PERSONAL_STORE_LOSE then
            if win_coins > 0 then
                R().basesvr({key=curr_player_uid}):send('.msg_handler','update_player_control_value', curr_player_uid, win_coins, self_game_type)
                --R().exdbsvr(1):send('.gm_coins_mgr','reduce_gm_coins',win_coins,{uid = curr_player_uid,table_type = self_table_type})
            elseif win_coins < 0 then
                R().basesvr({key=curr_player_uid}):send('.msg_handler','update_player_control_value', curr_player_uid, win_coins, self_game_type)
                --R().exdbsvr(1):send('.gm_coins_mgr','add_gm_coins',math_abs(win_coins),false,{uid=curr_player_uid,table_type=self_table_type})
            end
        end

    end

    --次玩家赢得金额，扣除一定比例的服务费，这时候需要取服务费的50%加到虚假奖池
    if math_floor(pay_fee*0.5) > 0 then
        R().mailsvr(1):send('.fruit_mgr','add_unreal_jackpot',self_table_type,math_floor(pay_fee*0.5))
    end

    --给玩家加钱
    local add_score = math_floor(total_award - pay_fee) 
    if add_score > 0 then
        local ret = add_coins(curr_player_uid,add_score + math_floor(pay_fee),reason.WIN_COIN,{is_robot=false},get_extra_bill_info(curr_player_uid))
        player_info.coins = ret.curr_coins or 0

        if pay_fee > 0 then
            local ret = reduce_coins(curr_player_uid,pay_fee,reason.PAY_FEE,{is_robot=false},get_extra_bill_info(curr_player_uid))
            -- billlog({op="reducecoins",uid = curr_player_uid,curr = player_info.coins,value = pay_fee,is_robot = false,r = reason.PAY_FEE},get_extra_bill_info(curr_player_uid))
        end

        if win_coins > 0 then
            --检查是否触发财神驾到
            local trigger_coins = math_floor(win_coins - pay_fee)
            if trigger_coins > 0 then
                local vip_level = player_info.vip_level  
                print("===========",curr_player_uid,trigger_coins,self_table_type,vip_level or 0)
                if system_store.check_trigger_caishenjiadao(curr_player_uid,trigger_coins,self_table_type,vip_level or 0) then     
                    notify_caishenjiadao(curr_player_uid,trigger_coins)
                end
            end
            --添加玩家首冲进度
            -- R().activitysvr(1):send('.activity_mgr','act_add_first_charge_process',curr_player_uid,win_coins)
        end
    end

    --更新免费次数
    if game_result.free_times > 0 then
        if player_info.free_times == 0 then
            player_info.last_bet_score = bet_score
            player_info.last_bet_line = bet_lines
        end
        player_info.free_times = player_info.free_times + game_result.free_times 
        player_info.free_times_round = curr_round
        add_free_times(curr_player_uid,game_result.free_times,reason.AWARD_FREE_TIEMS)
    end

    --添加玩家流水
    add_player_coins_water(win_coins)

    R().basesvr({key=curr_player_uid}):send('.msg_handler','add_total_coins',curr_player_uid,win_coins - (pay_fee or 0))
    if curr_tiger_instance:is_free_times() == false then    --不是免费下注
        local str_date = os.date("%Y%m%d%H%M%S")
        billlog({ op   = "water_history",  
            table_gid  = str_date .. "_" .. self_table_id,
            table_type = self_table_type,
            uid        = curr_player_uid,
            value      = bet_score * bet_lines,
            is_robot   = false,
            r          = constant.WATER_TYPE.WATER_WIN
        })
        R().basesvr({key=curr_player_uid}):send('.msg_handler','add_person_water',curr_player_uid,bet_score * bet_lines)
    end

    table_water = table_water + math_abs(win_coins)
    table_award_coins = table_award_coins + total_award
    local rsp_game_result = { 
        result_award = add_score,
        result_shape = game_result.result_shape,
        result_lines = game_result.result_lines,
        result_times = game_result.result_times,
        caijin_coins = jackpor_award,         --玩家所中彩金值
        free_times = game_result.free_times,
    }
    print("==============rsp_game_result======",curr_player_uid,curr_tiger_instance:is_free_times(),store_result,table_water,table_total_bet,table_award_coins,player_info.free_times,game_result.type,tostring_r(rsp_game_result))
    notify_all('tiger.NTF_GAMEOVER',{
        game_result = rsp_game_result,
        type = game_result.type
    })

    notify_players_coins_change(curr_player_uid,player_info_list[curr_player_uid].coins)

    local info = {
        bet_score = bet_score,
        bet_lines = bet_lines,
        free_bet = curr_tiger_instance:is_free_times(),
        result_shape = game_result.result_shape,
        result_lines = game_result.result_lines,
        caijin_coins = jackpor_award,
        free_times = game_result.free_times,
    }

    check_store_transfer() --检查转移库存
    write_card_record_log(win_coins,pay_fee,rsp_game_result,info)
    send_real_horse_message(win_coins-pay_fee)
    -- realTimeWinLose(curr_player_uid)

    player_info.all_win_lose = player_info.all_win_lose + win_coins - (pay_fee or 0)
    player_info.all_free = player_info.all_free + pay_fee
    player_info.all_bet = player_info.all_bet + bet_score * bet_lines
    player_info.all_produce = player_info.all_produce + total_award

    local uid = curr_player_uid
    local bet_coins = bet_score * bet_lines
    local produce = total_award
    local win_lose = win_coins - pay_fee
    local free = pay_fee
    expand.sendSummary(self_table_type, uid, bet_coins, produce, win_lose, free)
    return true
end

local function have_player()
    if curr_player_uid or player_info_list[curr_player_uid]then
        return true
    end

    return false
end

local function check_stop_table()
    if stopping_server then
        billlog({op = "stop_table",status = curr_status,password = self_password})
        closing_server = true --直接关闭桌子
        return true
    end

    return false
end

local function deal_leave(uid)
    clear_and_unlock_offline_player(uid)
    --notify_matchsvr_table_stats()
    
    skynet.send(".table_mgr", "lua", "leave", uid) 
end

--消毁桌子
local function dissmiss_table()
    local uids = {}

    for uid,player_info in pairs(player_info_list) do
        local game_session = table_players[uid]
        deal_leave(uid)
        send_to_gateway(uid,game_session,'tiger.RSP_LEAVE',{result=error_code.TABLE_IS_NOT_EXISTING})
        watch_session(game_session,uid,false)

        skynet.send('.table_mgr','lua','leave',uid)
        table_insert(uids,uid)
    end
    print("=========dissmiss_table====",tostring_r(uids))
    notify_matchsvr_table_stats(true)  --更新桌子
    skynet.send('.table_mgr','lua','on_table_delete',self_table_id,uids)    --删除状态

    return true
end

local function check_close_table(curr_time)
    local closable = false
    if closing_server then
        closable = true
    else
        if not have_player() and curr_time - game_start_init_time >= 30 then
            closable = true
        end
    end
    if not closable then
        return
    end
    print("____check_close_table__2___", is_on_match())
    if is_on_match() and not closing_server then
        print("table is on match",closable)
        return
    end
    print("____check_close_table__3___")
    local ok,ret = xpcall(dissmiss_table,debug.traceback)
    if not ok then
        errlog(ret)
    end
    print("____check_close_table__4___")
    billlog({op = "close_table",status = curr_status,password = self_password})
    dbglog('ffffffffnow delete this table',self_table_id)
    skynet.exit()
end

local function had_betted(uid)               
    if not curr_tiger_instance then
        print("============111111111111111")
        return false
    end

    return curr_tiger_instance:had_betted(uid)
end

local function had_betting_lock(uid)
    return uid_betting_map[uid]
end

local function is_lock_timeout(now,uid)
    if type(uid_betting_map[uid]) == 'number' then
        return now - uid_betting_map[uid] > 3
    end
    return false
end

-- local function deal_old_lock_player(uid)
--     if type(uid_betting_map[uid]) == 'boolean' then
--         return true
--     end
--     return false
-- end

local function check_reconnect_timeout(curr_time)
    --检查离线超时玩家是否需要做踢掉操作
    for uid, offline_time in pairs(reconnect_timout) do
        if curr_time - offline_time >= 120 and table_players[uid] == OFFLINE_CLIENT_FD and not had_betting_lock(uid) then
            dbglog(uid,'now clear this offlined player')
            clear_and_unlock_offline_player(uid)
        end
    end
end

function update(curr_time)
    if curr_status == TABLE_STATUS_CHECK_START then
        check_stop_table()  --停服
        if check_start(curr_time) then
            curr_tiger_instance:start_betting()
            curr_status = TABLE_STATUS_WAITTING_BET
        end
    elseif curr_status == TABLE_STATUS_WAITTING_BET then
        check_stop_table()  --停服
        if had_betted(curr_player_uid) then
            store_result = get_store_result()
            dbglog("start_store_result=======",store_result)
            uid_betting_map[curr_player_uid] = curr_time
            curr_round = curr_round + 1
            --将玩家下注加入系统库存
            local bet_score,bet_lines = curr_tiger_instance:get_uid_bet_record(curr_player_uid)
            add_system_store(bet_score*bet_lines) 

            curr_status = TABLE_STATUS_GAMEOVER
        elseif is_lock_timeout(curr_time,curr_player_uid) then
            uid_betting_map[curr_player_uid] = nil
        -- elseif deal_old_lock_player(curr_player_uid) then --此处是兼容旧数据 下个版本删除
        --     clear_offline_players()
        end
    elseif curr_status == TABLE_STATUS_GAMEOVER then
        select_shape()
        curr_tiger_instance:judge_result()
        local ok,ret = xpcall(audit_game_result,debug.traceback,curr_tiger_instance:get_game_result())
        if not ok then
            errlog(ret)
        end
        uid_betting_map = {}
        curr_status = TABLE_STATUS_GAMEOVER_WAIT
    elseif curr_status == TABLE_STATUS_GAMEOVER_WAIT then 
        curr_status = TABLE_STATUS_RESTART
    elseif curr_status == TABLE_STATUS_RESTART then
        curr_tiger_instance = nil
        curr_status = TABLE_STATUS_CHECK_START
        check_stop_table()  --停服
        --clear_offline_players()
        --game_start_init_time = util.get_now_time()
    else
        errlog('unknown status...',curr_status)
    end
    check_reconnect_timeout(curr_time)
    check_close_table(curr_time)
end

local function game_update()
    curr_status = TABLE_STATUS_CHECK_START
    while true do
        local curr_time = util.get_now_time()
        local ok,ret = xpcall(update,debug.traceback,curr_time)
        if not ok then 
            errlog(ret)
        end
        skynet.sleep(50)
    end
end

------------------------------internal------------------------------
function internal.start(conf)
    dbglog(tostring_r(conf))
    self_table_id = conf.table_id
    self_table_type = conf.table_type
    self_table_gid = conf.table_gid
    self_game_type = conf.game_type
    self_table_pattern = conf.table_conf.table_pattern   --记录了桌子的模式以及机器人的数量robot_num,table_pattern
    curr_round = 0

    game_start_init_time = util.get_now_time()

    skynet.fork(game_update)

    return true
end

function internal.stop_server()
    print("____stop_server___111111111_", curr_status)
    stopping_server = true
end

function internal.close_server()
    closing_server = true
end

function internal.disconnect(uid,game_session)
    dbglog(uid,'disconnect...',table_players[uid],game_session)
    if table_players[uid] ~= game_session then
        errlog(uid,'invalid status',table_players[uid],game_session)
        return
    end
    if uid ~= curr_player_uid then
        errlog(uid,'invalid player',curr_player_uid)
        return
    end
    
    -- if had_betting_lock(uid) then
    --     table_players[uid] = OFFLINE_CLIENT_FD
    --     return true
    -- end

    -- deal_leave(uid)
    table_players[uid] = OFFLINE_CLIENT_FD
    reconnect_timout[uid] = util.get_now_time()

    return true
end

function internal.enter(uid,game_session,msg)
    local ok,r = enter(uid,game_session,msg)
    if not ok then
        send_to_gateway(uid,game_session,'table.RSP_ENTER',{result = r})
        dbglog("---enter false---- uid",uid)
        return false
    end
    dbglog("=====rsp_enter==========",tostring_r(r))
    send_to_gateway(uid,game_session,'table.RSP_ENTER',r)

    --跟网关agent绑定seesion监控 网关有关闭事件进行通知
    watch_session(game_session,uid,true)

    R().friendsvr(1):send('.info_mgr','update_info',uid,{curr_game_type = self_game_type, curr_table_type = self_table_type})
    
    return true
end

function internal.exit()
    skynet.exit()
end

function internal.update_coins_on_table(uid,amount,safe_box)
    local player_info = player_info_list[uid]
    if player_info then
        player_info.coins = player_info.coins + amount
        --if player_info.position then
        send_to_gateway(uid,table_players[uid],'table.NTF_MONEY_CHANGED',{chged_list={{uid=uid,coins=player_info.coins}}})
        -- notify_all('table.NTF_MONEY_CHANGED',{chged_list={{uid=uid,coins=coins}}})
        --end
        if safe_box then
            player_info.safe_box = safe_box
        end
    end
    return true
end

function internal.newmatch_register_all(player_data_list)
    local result = {}
    local failed_list = {}
    local success_list = {}

    dbglog("=======================111111newmatch,self_table_pattern",self_table_pattern,self_table_id)
    local end_idx
    set_on_match()
    for i, player_data in pairs(player_data_list) do
        local uid = player_data.uid
        print("===================11111111",uid,curr_player_uid)
        if table_players[uid] then   --老虎机一个桌子只能有一个玩家
            if curr_player_uid == uid then     --玩家进房间了
                dbglog(uid, "have been already in table", i)
                table_insert(success_list, uid)    
            else    --该房间有别的玩家了
                 table_insert(failed_list, player_data)
            end
       
            goto continue
        end 
        assert(not player_info_list[uid])
        local player_info = get_player_enter_data(uid)
       
        if not player_info then
            errlog(uid,'failed to get_player_enter_data')
            table_insert(failed_list, player_data)
            goto continue
        end

        if not lock_one_player(uid) then
            errlog(uid,'failed to lock_one_player')
            table_insert(failed_list, player_data)
            goto continue
        end

        if get_player_num() >= table_def.game_player_num[self_game_type] then
            end_idx = i
            unlock_one_player(uid)
            break
        end

        player_info.init_coins = player_data.coins
        player_info_list[uid] = player_info
        table_players[uid] = REGISTER_CLIENT_FD
        table_insert(success_list,uid)

        dbglog("new_player:", self_table_id, uid)

        ::continue::
    end

    if end_idx then
        for i = end_idx,#player_data_list do
            local player_data = player_data_list[i]
            table_insert(failed_list,{uid = player_data.uid})
        end
    end 
    unset_on_match()

    result.failed_list = failed_list
    result.success_list = success_list
    local table_stats = {}
    table_stats.table_type = self_table_type
    table_stats.left_num = table_def.game_player_num[self_game_type] - get_player_num()
    table_stats.table_gid = self_table_gid
    table_stats.game_type = self_game_type
    table_stats.table_pattern = self_table_pattern

    result.table_stats = table_stats

    return result
end

function internal.get_player_num()
    return get_player_num()
end

function internal.sync_table_stats()
    local left_num = MAX_PLAYER_NUM - get_player_num()
    if left_num < 0 then left_num = 0 end

    local table_stats = {}
    table_stats.table_gid = self_table_gid
    table_stats.left_num = left_num
    table_stats.table_pattern = self_table_pattern
    return self_table_type,table_stats
end

function internal.get_game_type()
    return self_game_type,self_table_type
end

function internal.get_user_playing_info()
    local playing_info = {}
    playing_info.game_type = self_game_type
    playing_info.table_type = self_table_type
    playing_info.total = get_player_num()

    return playing_info
end

--在线玩家概况数据
function internal.get_summary_online()
    local res = {}
    for uid, player in pairs(player_info_list) do 
        table_insert(res, {
            uid = uid, 
            all_bet = player.all_bet,
            all_win_lose = player.all_win_lose,
            all_free = player.all_free,
            all_produce = player.all_produce,
        })
    end
    return res
end


--------------------handler----------------------------------
--点击开始按钮,下注
function handler.REQ_BET(uid,msg,game_session)
    dbglog("==========11111111handler.REQ_BET",uid,tostring_r(msg),game_session)
    if table_players[uid] ~= game_session then
        errlog(uid,'invalid status',table_players[uid],game_session)
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = error_code.PERMISSION_DENIDE})
        return false
    end

    --检查状态
    if curr_status ~= TABLE_STATUS_WAITTING_BET then
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = -200})
        return false
    end

    if uid ~= curr_player_uid then
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = -201})
        return false
    end

    --取出玩家对象先
    local player_info = assert(player_info_list[uid])
    local bet_score = msg.bet_score   --单线押注
    local is_free = msg.is_free
    local bet_lines = msg.bet_lines   --押线数
    local last_bet_score = player_info.last_bet_score or 0
    local last_bet_line = player_info.last_bet_line or 1

    if not curr_tiger_instance then
        errlog(uid,'curr_tiger_instance is nil')
        return 
    end

    --防止重复押注
    if type(uid_betting_map[uid]) == 'number' then
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = -201})
        return false
    end

    local ok,ret = curr_tiger_instance:can_bet(uid,is_free,bet_score,bet_lines,last_bet_score,last_bet_line)
    if not ok then
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = ret})
        return
    end

    uid_betting_map[uid] = util.get_now_time()
    
    local curr = {}
    local total_bet = math_floor(bet_score*bet_lines)
    if not is_free then
        local ret = reduce_coins(uid,total_bet,reason.BET_COIN,{is_robot=false},get_extra_bill_info(uid))
        if not ret then
            send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = error_code.GOLD_IS_NOT_ENOUGH})
            errlog(uid,'REQ_BET reduce_coins failed to',uid,total_bet)
            uid_betting_map[uid] = nil
            return false
        end
        curr.curr_coins = ret.curr_coins
    else
        local succ,ret = reduce_free_times(uid,1,reason.TIGER_FREE_BET)
        if not succ or not ret then
            send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = error_code.FREE_TIMES_NOT_ENOUGH})
            errlog(uid,'REQ_BET reduce_free_times failed to',uid)
            uid_betting_map[uid] = nil
            return false
        end

        curr.free_times = ret.total_free_times or 0
        local free_times_data = ret.ret_free_data
        if not free_times_data then
            curr.last_bet_score = 0
            curr.last_bet_line = 1
        else
            curr.last_bet_score = free_times_data.last_bet_score or 0
            curr.last_bet_line = free_times_data.last_bet_line or 1
        end
    end

    --异步完后，必须是可押注状态
    if curr_status ~= TABLE_STATUS_WAITTING_BET then
        errlog(uid,'failed to check status',total_bet,curr_status,is_free)
        if not is_free then
            add_coins(uid,total_bet,reason.BET_COIN_BACK,{is_robot=false},get_extra_bill_info(uid))
        else
            add_free_times(uid,1,reason.BET_FREE_TIMES_BACK)
        end
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = -113})
        uid_betting_map[uid] = nil
        return
    end

    local ok,ret = curr_tiger_instance:bet(uid,is_free,bet_score,bet_lines,last_bet_score,last_bet_line)
    if not ok then
        errlog(uid,'failed to betting coins',is_free,bet_score,bet_lines)
        if not is_free then
            add_coins(uid,bet_score,reason.BET_COIN_BACK,{is_robot=false},get_extra_bill_info(uid))
        else
            add_free_times(uid,1,reason.BET_FREE_TIMES_BACK)
        end
        send_to_gateway(uid,game_session,'tiger.RSP_BET',{result = ret})
        uid_betting_map[uid] = nil
        return
    end

    if not is_free then
        --通知玩家本人金币发生变化
        player_info.coins = curr.curr_coins
        utils.notify_money_changed(uid,{coins = curr.curr_coins},game_session,constant.MONEY_CHANGED_REASON.TIGER_BET)
        table_total_bet = table_total_bet + msg.bet_score * msg.bet_lines
    else
        player_info.free_times = curr.free_times
        player_info.last_bet_score = curr.last_bet_score
        player_info.last_bet_line = curr.last_bet_line
    end

    send_to_gateway(uid,game_session,'tiger.RSP_BET',{
        free_times = player_info.free_times,
        last_bet_score = player_info.last_bet_score or 0,
        last_bet_line = player_info.last_bet_line or 1,
        is_free = is_free})

    return true
end

function handler.REQ_LEAVE(uid,msg,game_session)
    dbglog("handler.REQ_LEAVE",uid,tostring_r(msg),game_session)
    if table_players[uid] ~= game_session then
        errlog(uid,'invalid status',table_players[uid],game_session)
        return false
    end

    local player_info = player_info_list[uid]
    if not player_info then
        dbglog(uid,'player_info is nil')
        return false
    end

    local init_coins = player_info_list[uid].init_coins or 0
    if had_betting_lock(uid) then          --玩家下注了不能离开
        dbglog(uid, "uid is betting can not leave")
        send_to_gateway(uid, game_session, "tiger.RSP_LEAVE", { result = error_code.HAD_BET_CANT_LEAVE_ERROR })
        return true
    end

    deal_leave(uid)
    send_to_gateway(uid,game_session,'tiger.RSP_LEAVE',{})
    watch_session(game_session,uid,false)    
    skynet.send('.table_mgr','lua','leave',uid)

    local init_coins = player_info.init_coins or 0
    print("check_lucky_tigger",init_coins)
    R().hallsvr({key=uid}):send('.msg_handler', 'check_lucky_tigger',uid,self_game_type,init_coins)
    
    return true
end

function handler.REQ_CAIJIN_PLAYERS(uid,msg,game_session)
    if table_players[uid] ~= game_session then
        errlog(uid,'invalid status',table_players[uid],game_session)
        return false
    end
    local ok,ret = R().exdbsvr(1):call('.fruit_mgr','get_fruit_record',self_table_type)
    if not ok then
        errlog(uid,"===========get_fruit_record not ok==== ")
        return
    end

    local player_list = {}
    for _, player_info in pairs(ret) do
        table_insert(player_list,{
            name = player_info.name,
            icon = player_info.icon,
            caijin_coins = player_info.coins,
            time = player_info.time
        })
    end
    print("=========REQ_CAIJIN_PLAYERS=",tostring_r(ret),tostring_r(player_list))

    send_to_gateway(uid,game_session,'tiger.RSP_CAIJIN_PLAYERS',{record_caijin_player = player_list})
    
    return true    
end

local function update_player_coins(uid, coins)
    local player_info = player_info_list[uid]
    if player_info then
        player_info.coins = coins
    else
        errlog(string_format("uid(%d) coins(%d) player info not exist", uid, coins))
    end
end

local function check_have_enough_coins(uid,coins)
    local player_info = player_info_list[uid]
    if not player_info or player_info.coins < coins then
        return false
    end
    return true
end

local function caishen_coins(uid, coins, award_coins, ...)
    local robot = false
    local ok, succ, ret = R().basesvr({ key = uid }):call(".msg_handler", "caishen_coins", uid, coins, award_coins, {is_robot=robot},get_extra_bill_info(uid))
    if not ok then
        errlog(string_format("uid(%d) call basesvr caishen_coins coins error", uid))
        return false
    end
    if not succ then
        errlog(string_format("uid(%d) basesvr caishen_coins coins succ == false error", uid))
        return false
    end

    --update_player_coins(uid, ret.curr)
    return ret
end

local function pay_caishen_coins(uid,coins)
    local caishenjiadao_store = global_configs.caishenjiadao_store
    local fee_rate = caishenjiadao_store.fee_rate
    local sysback_coins = math_floor((coins *fee_rate) / 10000)
    local base_coins = coins - sysback_coins
    local curr_coins
    local player_info = player_info_list[uid]
    local ret_coins = system_store.check_caishen_store_result(self_table_type,uid,coins)
    player_info.all_bet = player_info.all_bet + coins
    local produce = 0
    local win_lose = 0
    if ret_coins then
        print("add coins",ret_coins)
        curr_coins = caishen_coins(uid,coins,ret_coins)
        if not curr_coins then
            errlog("reduce coins failed",ret )
            return error_code.NOT_ENOUGH_COINS
        end
        sendLoseWinToFriendServer(coins - ret_coins)            
        --改变财神库存
        R().exdbsvr(1):send('.caishen_mgr','push_data_pool',self_table_type,uid,base_coins,sysback_coins)
        R().exdbsvr(1):send('.caishen_mgr','sub_caishen_base',self_table_type,uid,ret_coins)
        player_info.all_win_lose = player_info.all_win_lose - coins + ret_coins        
        player_info.all_produce = player_info.all_produce + ret_coins        
        produce = ret_coins       
        win_lose =  - coins + ret_coins    
    else
        local ret = reduce_coins(uid, coins, reason.OPEN_CAISHEN)
        if not ret then
            errlog("reduce coins failed",ret )
            return error_code.NOT_ENOUGH_COINS
        end
        sendLoseWinToFriendServer(coins)
        --改变财神库存
        R().exdbsvr(1):send('.caishen_mgr','push_data_pool',self_table_type,uid,base_coins,sysback_coins)
        curr_coins = ret.curr_coins
        player_info.all_win_lose = player_info.all_win_lose - coins
        win_lose =  - coins  
    end

    local uid = uid
    local bet_coins = coins
    local produce = produce
    local win_lose = win_lose
    local free = 0
    expand.sendSummary(self_table_type, uid, bet_coins, produce, win_lose, free)

    utils.notify_money_changed(uid,{coins = curr_coins})
    notify_all("table.NTF_MONEY_CHANGED", { chged_list = { { uid = uid, coins = curr_coins } } })
    
    update_player_coins(uid, curr_coins)
    caishen_coins_map[uid] = nil
    return error_code.RSP_SUCESS,ret_coins    
end

function handler.REQ_OPEN_CAISHEN(uid,msg)
    local coins = msg.coins
    --[[if not caishen_coins_map[uid] or caishen_coins_map[uid] ~= coins then
        errlog("put coins error",uid,caishen_coins_map[uid])
        return
    end]]
    if not caishen_coins_map[uid] or coins ~= caishen_coins_map[uid] then
        errlog("your param is error",uid,coins)
        send_to_gateway(uid,table_players[uid],'table.RSP_OPEN_CAISHEN',{result = error_code.INPUT_ERROR})
        return false
    end

    if not check_have_enough_coins(uid,coins) then
        errlog("you have not enough coins",uid)
        send_to_gateway(uid,table_players[uid],'table.RSP_OPEN_CAISHEN',{result = error_code.NOT_ENOUGH_COINS})
        return false
    end
    local res,ret_coins = pay_caishen_coins(uid,coins)
    if res == error_code.RSP_SUCESS then
        send_to_gateway(uid,table_players[uid],'table.RSP_OPEN_CAISHEN',{result = res,award_coins = ret_coins})
    else
        send_to_gateway(uid,table_players[uid],'table.RSP_OPEN_CAISHEN',{result = res})
    end
end

--换游戏
-- function handler.REQ_GAME_CHANGE(uid,msg,game_session)
--     local roomdata_list = global_configs.roomdata or {}
--     local roomdata = roomdata_list[msg.table_type]
--     if not roomdata then
--         errlog(self_table_type,"check_game_change_table but roomdata is nil")
--         return false
--     end

--     local player_info = player_info_list[uid]
--     if not player_info then
--         dbglog("REQ_GAME_CHANGE error player_info is nil", uid)
--         send_to_gateway(uid,game_session,'table.RSP_GAME_CHANGE',{result = error_code.PERMISSION_DENIDE})
--         return false
--     end

--     if player_info.coins < (roomdata.min or 0) then
--         send_to_gateway(uid,game_session,'table.RSP_GAME_CHANGE',{result = error_code.GOLD_IS_NOT_ENOUGH})
--         return false
--     end

--     if (roomdata.max or 0) ~= 0 and player_info.coin > (roomdata.max or 0) then
--         send_to_gateway(uid,game_session,'table.RSP_GAME_CHANGE',{result = error_code.GOLD_OUT_OF_MAX})
--         return false
--     end

--      if had_betting_lock(uid) then          --玩家下注了不能离开
--         dbglog(uid, "uid is betting can not leave")
--         send_to_gateway(uid, game_session, "tiger.RSP_GAME_CHANGE", { result = error_code.HAD_BET_CANT_LEAVE_ERROR })
--         return false
--     end
--     print("change_table_type=============",msg.table_type)

--     deal_leave(uid)
--     watch_session(game_session,uid,false)    

--     local user_data = {
--         uid = uid,
--         table_type = msg.table_type,
--         begin_time = util.get_now_time(),
--         name = player_info.name,
--         coins = player_info.coins,
--         sex = player_info.sex,
--         icon = player_info.icon,
--         --old_table_gid = self_table_gid,
--         --ip_limit = false,
--         --login_ip = player_info.login_ip,
--     }   

--     --请求匹配服进行匹配了
--     local ok = R().newmatchsvr(1):call(".table_mgr", "match_player",user_data)
--     if not ok then
--         errlog(string_format("uid(%d) call newmatchsvr table_mgr match_player error",uid))
--         send_to_gateway(uid, game_session, "table.RSP_GAME_CHANGE", { result = error_code.RSP_FAIL })
--         return true
--     end
--     send_to_gateway(uid, game_session, "table.RSP_GAME_CHANGE", { result = error_code.RSP_SUCESS })

--     return true
-- end


function internal.update_caijin(coins)
--    print("update caijin",coins)
    if curr_player_uid then
        local game_session = table_players[curr_player_uid]
        send_to_gateway(curr_player_uid,game_session,'tiger.NTF_CAIJIN',{system_caijin = coins})
    end
end
--=================================table protocols=================
local function update_player_heartbeat(uid)
    local player_info = player_info_list[uid]
    if player_info then
        player_info.last_ping_time = util.get_now_time()
    end
end

local function get_msg_module_name(msgid)
    local m = msgdef.id_to_name[msgid]
    if not m then return end
    return m[1],m[2]
end

function dispatch_client_message(game_session,uid,msg,size)
    local _,msgid,pbmsg,pbsize = proxypack.unpack_client_message(msg,size)
    local module,name = get_msg_module_name(msgid)

    if not module or not name then
        print('invalid msgid',msgid,module,name)
        return
    end

    local pbname = module .. '.' .. name
    local req_msg = pb.decode(pbname,pbmsg,pbsize)

    dbglog(string_format('[%s]>>>>>player[%s] got a request[%s] content(%s)',
        skynet.address(skynet.self()),tostring(uid),pbname,tostring_r(req_msg)))

    local f = handler[name]
    if not f then
        errlog('unknown action',pbname)
        return
    end

    local ret = f(uid,req_msg,game_session)
    if not ret then
        errlog(string_format('failed to handle requrest(%s.%s)',module,name))
    end

    update_player_heartbeat(uid)
end

local function dispatch(_, _, game_session, uid, data)
    local msg, size = proxypack.pack_raw(data)
    local ok, errmsg = xpcall(dispatch_client_message, debug.traceback, game_session, uid, msg, size)
    skynet.trash(msg, size)  --这里需要保证内存被释放
    if not ok then 
        errlog(errmsg)
    end
end

function handle_lua_req(session, source, action, ...)
    local f = internal[action]
    if f then
        skynet.retpack(f(...))
    else
        dbglog(action)
        handler[action](...)
    end
end

function start()
    skynet.register_protocol { 
        name = "client", 
        id = skynet.PTYPE_CLIENT, 
        unpack = skynet.unpack, 
        dispatch = dispatch
    }
    
    skynet.start(function()
        skynet.dispatch("lua", function(session, source, action, ...)
            handle_lua_req(session,source,action,...)
        end)

        -- sharedata.query("global_configs")
        shapedata = sharedata.query("shapedata")

        -- global_configs = setmetatable({}, { 
        --     __index = function(t, k) 
        --         return sharedata.query("global_configs")[k]
        --     end
        -- })
        global_configs = setmetatable({},{
            __index = function(t,k) 
                --return sharedata.query("global_configs")[k]
                return sharedata.query(k)
            end
        })
        _G.global_configs = global_configs
        _G.shapedata = shapedata
    end)
end

return _ENV